package boot.spring.util;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.micrometer.core.lang.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.*;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.util.*;

public class RestTemplateUtils {

    private static Logger logger = LoggerFactory.getLogger("RestTemplateUtils");
    private volatile static RestTemplate restTemplate;

    /**
     * GET请求
     *
     * @param url
     * @param returnDataType 返回数据的格式 JSONObject|JSONArray.class|String.class|Map.class|ArrayList.class，可为空
     * @param requestHeaders 请求头，可为空
     * @param requestParams  求参数，可为空
     * @return 结果
     */
    public static Object sendGet(String url,
                                 @Nullable Class<?> returnDataType,
                                 @Nullable Map<String, Object> requestHeaders,
                                 @Nullable Map<String, Object> requestParams) {
        return httpRequest(url, HttpMethod.GET, requestHeaders, requestParams, null, returnDataType, JSONObject.class);
    }


    /**
     * POST请求
     *
     * @param url
     * @param returnDataType 返回数据的格式 JSONObject|JSONArray.class|String.class|Map.class|ArrayList.class，可为空
     * @param requestHeaders 请求头，可为空
     * @param requestParams  请求参数，可为空
     * @param requestBody    请求体，可为空
     * @param sendDataType   请求体格式 json|jsonArray
     * @return 结果
     */
    public static Object sendPost(String url,
                                  @Nullable Class<?> returnDataType,
                                  @Nullable Map<String, Object> requestHeaders,
                                  @Nullable Map<String, Object> requestParams,
                                  @Nullable String requestBody,
                                  Class<?> sendDataType) {
        return httpRequest(url, HttpMethod.POST, requestHeaders, requestParams, requestBody, returnDataType, sendDataType);
    }


    /**
     * POST请求 字节流
     *
     * @param url
     * @param response       http响应消息，可为空
     * @param requestHeaders 请求头，可为空
     * @param requestParams  请求参数，可为空
     * @param requestBody    请求体，可为空
     * @param sendDataType   请求体格式 json|jsonArray
     **/
    public static void sendPostByByte(String url, HttpServletResponse response,
                                      @Nullable Map<String, Object> requestHeaders,
                                      @Nullable Map<String, Object> requestParams,
                                      @Nullable String requestBody,
                                      Class<?> sendDataType) {
        download(url, HttpMethod.POST, requestHeaders, requestParams, requestBody, response, false, sendDataType);
    }

    /**
     * GET请求 字节流
     *
     * @param url
     * @param response       http响应消息，可为空
     * @param requestHeaders 请求头，可为空
     * @param requestParams  请求参数，可为空
     **/
    public static void sendGetByByte(String url, HttpServletResponse response,
                                     @Nullable Map<String, Object> requestHeaders,
                                     @Nullable Map<String, Object> requestParams) {
        download(url, HttpMethod.GET, requestHeaders, requestParams, null, response, false, JSONObject.class);
    }

    /**
     * 发送http请求
     *
     * @param url
     * @param method
     * @param requestHeaders
     * @param requestParams
     * @param requestBody
     * @param returnDataType
     * @return
     */
    public static <T> Object httpRequest(String url, HttpMethod method,
                                         @Nullable Map<String, Object> requestHeaders,
                                         @Nullable Map<String, Object> requestParams,
                                         @Nullable String requestBody,
                                         @Nullable Class<?> returnDataType,
                                         Class<?> sendDataType) {

        Map<String, Object> params = new HashMap<>();
        HttpHeaders headers = new HttpHeaders();
        // 1. post请求 -json传参
        // 2. post请求 -formData传参
        // 3. get请求 -占位符传参
        // 4. get请求 -url拼接

        // 第一步 构建httpEntity
        HttpEntity<?> entity = buildHttpEntity(sendDataType, method, requestHeaders, requestParams, requestBody, params, headers);
        // 第二步 单例调用restTemplate
        RestTemplate restTemplate = getSingleRestTemplate();
        // 第三步 初始化一个返回值
        Object r = null;
        // 第四步 执行http请求，并根据情况给返回值赋值
        try {
            Class<?> resultClass = String.class;
            if (returnDataType != null) {
                resultClass = returnDataType;
                r = handleHttpRequest(restTemplate, resultClass, url, method, params, entity);
            } else {
                handleHttpRequest(restTemplate, resultClass, url, method, params, entity);
            }

        } catch (RestClientException e) {
            logger.error("远程调用接口异常{}", e);
        }
        return r;
    }

    /**
     * 下载
     *
     * @param url
     * @param method
     * @param requestHeaders
     * @param requestParams
     * @param requestBody
     * @param response
     * @param isRespBodyOnly 是否是只需要响应体,默认true，true：只需要响应体  false：同时返回响应状态 响应头  响应体
     */
    public static void download(String url, HttpMethod method,
                                @Nullable Map<String, Object> requestHeaders,
                                @Nullable Map<String, Object> requestParams,
                                @Nullable String requestBody,
                                HttpServletResponse response,
                                @Nullable Boolean isRespBodyOnly,
                                Class<?> sendDataType) {
        if (Objects.isNull(isRespBodyOnly)) {
            isRespBodyOnly = true;
        }
        HttpHeaders headers = new HttpHeaders();
        Map<String, Object> params = new HashMap<>();

        // 第一步 构建httpEntity
        HttpEntity<?> entity = buildHttpEntity(sendDataType, method, requestHeaders, requestParams, requestBody, params, headers);
        // 第二步 单例调用restTemplate
        RestTemplate restTemplate = getSingleRestTemplate();
        // 第三步 执行http请求，并返回字节流
        handleHttpRequest(restTemplate, url, method, response, isRespBodyOnly, params, entity);
    }

    /**
     * 构建httpEntity
     *
     * @param method
     * @param requestHeaders
     * @param requestParams
     * @param requestBody
     * @param params
     * @param headers
     * @return
     */
    private static HttpEntity<?> buildHttpEntity(Class<?> sendDataType, HttpMethod method, Map<String, Object> requestHeaders, Map<String, Object> requestParams, String requestBody, Map<String, Object> params, HttpHeaders headers) {
        //1.设置请求参数
        if (requestParams != null && !requestParams.isEmpty()) {
            Iterator<Map.Entry<String, Object>> paramsIterator = requestParams.entrySet().iterator();
            Map.Entry<String, Object> nextParam = null;
            while (paramsIterator.hasNext()) {
                nextParam = paramsIterator.next();
                params.put(nextParam.getKey(), nextParam.getValue());
            }
        }
        //2.设置请求头
        if (requestHeaders != null && !requestHeaders.isEmpty()) {
            Iterator<Map.Entry<String, Object>> headersIterator = requestHeaders.entrySet().iterator();
            Map.Entry<String, Object> nextHeader = null;
            while (headersIterator.hasNext()) {
                nextHeader = headersIterator.next();
                headers.add(nextHeader.getKey(), String.valueOf(nextHeader.getValue()));
            }
        }
        //3.设置请求体
        //4.请求体 请求头封装到HttpEntity
        String contentType = String.valueOf(requestHeaders.get("Content-Type"));
        if (requestBody != null && !requestBody.isEmpty()) {
            if (sendDataType == null || sendDataType.equals(JSONObject.class)) {
                Map<String, Object> requestBodyMap = JSONObject.parseObject(requestBody, Map.class);
                Map bodyMap;
                if (MediaType.APPLICATION_JSON_VALUE.equals(contentType) || HttpMethod.GET.equals(method)) {
                    bodyMap = setEntityBody(Map.class, requestBodyMap);
                } else {
                    bodyMap = setEntityBody(LinkedMultiValueMap.class, requestBodyMap);
                }
                return new HttpEntity<>(bodyMap, headers);
            } else if (sendDataType.equals(JSONArray.class)) {
                List<Map> requestBodyMapList = JSON.parseArray(requestBody, Map.class);
                List<Map> bodyMapList = new ArrayList<>();
                if (MediaType.APPLICATION_JSON_VALUE.equals(contentType) || HttpMethod.GET.equals(method)) {
                    requestBodyMapList.forEach(i -> bodyMapList.add(setEntityBody(Map.class, i)));
                } else {
                    requestBodyMapList.forEach(i -> bodyMapList.add(setEntityBody(LinkedMultiValueMap.class, i)));
                }
                return new HttpEntity<>(bodyMapList, headers);
            } else if (sendDataType.equals(String.class)) {
                return new HttpEntity<>(requestBody, headers);
            }

        }
        // todo 如果是其他情况，则返回null
        return new HttpEntity<>(null, headers);
    }

    /**
     * 请求http请求
     *
     * @param restTemplate
     * @param typeClass
     * @param url
     * @param method
     * @param params
     * @param entity
     * @param <T>
     * @return
     */
    private static <T> T handleHttpRequest(RestTemplate restTemplate, Class<T> typeClass, String url, HttpMethod method, Map<String, Object> params, HttpEntity<?> entity) {
        ResponseEntity<T> exchange = restTemplate.exchange(url, method, entity, typeClass, params);

        return typeClass.cast(exchange.getBody());
    }

    /**
     * 处理http请求（字节流）
     *
     * @param restTemplate
     * @param url
     * @param method
     * @param response
     * @param isRespBodyOnly
     * @param params
     * @param entity
     */
    private static void handleHttpRequest(RestTemplate restTemplate, String url, HttpMethod method, HttpServletResponse response, Boolean isRespBodyOnly, Map<String, Object> params, HttpEntity<?> entity) {
        BufferedOutputStream bos = null;
        try {
            ResponseEntity<byte[]> exchange = restTemplate.exchange(url, method, entity, byte[].class, params);
            if (!isRespBodyOnly && response != null) {
                setResponseDetails(response, exchange);
            }
            // FIXME: 2023/3/29 by.xurenyi 修改下载文件流的格式，此处有些小问题，暂定使用原接口的文件格式~~~
//            response.reset();
//            response.setContentType("pdf/plain");
//            response.setContentType("application/octet-stream;charset=UTF-8");
//            response.setHeader("Content-Type", "application/octet-stream;charset=UTF-8");
//            response.setHeader("Content-Disposition", "attachment; filename=" + "test" + ".pdf");
            //响应体
            ServletOutputStream os = response.getOutputStream();
            bos = new BufferedOutputStream(os);
            byte[] buf = exchange.getBody();
            bos.write(buf);
            bos.flush();
        } catch (IOException e) {
            logger.error("RestTemplateUtils获取接口二进制流异常");
        } catch (RestClientException e) {
            logger.error("远程调用接口异常{}", e);
        } finally {
            try {
                bos.close();
            } catch (IOException e) {
                logger.error("RestTemplateUtils流关闭异常");
            }
        }
    }

    /**
     * 设置http响应详情
     *
     * @param response
     * @param exchange
     * @param <T>
     */
    private static <T> void setResponseDetails(HttpServletResponse response, ResponseEntity<T> exchange) {
        //响应状态码
        response.setStatus(exchange.getStatusCodeValue());
        //响应头,可能存在一个key对应多个value,本方法中会将同名header合并
        HttpHeaders resHeaders = exchange.getHeaders();
        Iterator<Map.Entry<String, List<String>>> resHeadersIterator = resHeaders.entrySet().iterator();
        while (resHeadersIterator.hasNext()) {
            Map.Entry<String, List<String>> headersNext = resHeadersIterator.next();
            response.setHeader(headersNext.getKey(), headersNext.getValue().toString());
        }
    }

    /**
     * 设置entity
     *
     * @param beanClass
     * @param requestBody
     * @return
     */
    private static Map setEntityBody(Class<? extends Map> beanClass, Map<String, Object> requestBody) {
        Object entityBody;
        Iterator<Map.Entry<String, Object>> bodyIterator = requestBody.entrySet().iterator();
        Map.Entry<String, Object> bodyNext;

        if (beanClass.equals(MultiValueMap.class) || beanClass.equals(LinkedMultiValueMap.class)) {
            MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
            while (bodyIterator.hasNext()) {
                bodyNext = bodyIterator.next();
                map.add(bodyNext.getKey(), bodyNext.getValue());
            }

            entityBody = map;
        } else if (beanClass.equals(Map.class)) {
            Map<String, Object> map = new HashMap<>();
            while (bodyIterator.hasNext()) {
                bodyNext = bodyIterator.next();
                map.put(bodyNext.getKey(), bodyNext.getValue());
            }

            entityBody = map;
        } else {
            Map<String, Object> map = new HashMap<>();
            while (bodyIterator.hasNext()) {
                bodyNext = bodyIterator.next();
                map.put(bodyNext.getKey(), bodyNext.getValue());
            }

            entityBody = map;
        }

        return beanClass.cast(entityBody);
    }

    /**
     * RestTemplate 单例 懒汉 双检锁
     **/
    public static RestTemplate getSingleRestTemplate() {
        if (restTemplate == null) {
            synchronized (RestTemplateUtils.class) {
                if (restTemplate == null) {
                    restTemplate = new RestTemplate();
                }
            }
        }
        return restTemplate;
    }
}
